iT邦幫忙

2024 iThome 鐵人賽

DAY 2
0

Light tomorrow with today.
用現在點亮未來。

今天的任務要做一個 Tab component,
最主要作用為使用者可以在不同標籤間切換來查看不同的內容。

下面為 示意圖:
step1. 先選一個
https://ithelp.ithome.com.tw/upload/images/20240908/20169148q1eZqOgxwb.png
step2. click 按下向左 scroll
https://ithelp.ithome.com.tw/upload/images/20240908/20169148dzT1cTZ8lT.png

step3. 畫面經過 RWD Resize,自動跳到當初選 Tab
https://ithelp.ithome.com.tw/upload/images/20240908/20169148QII9RwcQw0.png

  • 當Scroll 滑到底,箭頭到底顏色為淺灰色
    https://ithelp.ithome.com.tw/upload/images/20240908/20169148cwWCME121e.png

基礎大頭貼tab

選擇 primevue tabMenu 為基準,做 tab component,

// template
  <p-tabMenu :model="items" @tab-change="onTabChange" ref="tabMenu">
        <template #item="{ item, props }">
          <a
            v-ripple
            v-bind="props.action"
            class="flex align-items-center gap-2"
          >
            <img
              :alt="item.name"
              :src="`https://primefaces.org/cdn/primevue/images/avatar/${item.image}`"
              style="width: 32px"
            />
            <span class="font-bold">{{ item.name }}</span>
          </a>
    </template>
</p-tabMenu>

// typescript
const items = ref([
  { name: "Amy Elsner", image: "amyelsner.png" },
  { name: "Anna Fali", image: "annafali.png" },
  { name: "Asiya Javayant", image: "asiyajavayant.png" },
  { name: "Bernardo Dominic", image: "bernardodominic.png" },
  { name: "Elwin Sharvill", image: "elwinsharvill.png" },
  { name: "Ioni Bowcher", image: "ionibowcher.png" },
  { name: "Ivan Magalhaes", image: "ivanmagalhaes.png" },
  { name: "Onyama Limba", image: "onyamalimba.png" },
  { name: "Stephen Shaw", image: "stephenshaw.png" },
  { name: "XuXue Feng", image: "xuxuefeng.png" },
]);

做左右箭頭

// template
 <p-button
        class="scroll-button left p-button-rounded p-button-text"
        @click="scroll('left')"
        :disabled="!showLeftScroll"
      >
        <i class="pi pi-chevron-left"></i>
</p-button>
<p-button
class="scroll-button right p-button-rounded p-button-text"
@click="scroll('right')"
:disabled="!showRightScroll"
>
<i class="pi pi-chevron-right"></i>
</p-button>

// typescript
const showLeftScroll = ref(false);
const showRightScroll = ref(false);

const scroll = (direction) => {
  if (tabMenu.value && tabMenu.value.$el) {
    const menuElement = tabMenu.value.$el.querySelector(".p-tabmenu-nav");
    const scrollAmount = 200;
    if (direction === "left") {
      menuElement.scrollLeft -= scrollAmount;
    } else {
      menuElement.scrollLeft += scrollAmount;
    }
    nextTick(checkScroll);
  }
};

如果在實施這些更改後仍然看不到箭頭,請檢查以下幾點:

  1. 確保 showLeftScroll 和 showRightScroll 在適當的時候被設置為 true。

  2. 確保 checkScroll 函數在適當的時機被調用,比如在窗口大小改變或內容變化時。

  3. :disabled 屬性來控制按鈕的狀態。這樣按鈕始終可見,但在不需要滾動時會被禁用。

  4. 調整了 scroll 函數,使其直接操作 TabMenu 的內部元素,而不是外部容器。

  5. 修改了 checkScroll 函數,使其檢查 TabMenu 的內部滾動狀態。

  6. 在 style 部分:
    為 .scroll-container 添加了 padding,為按鈕留出空間。

  7. 調整了 .p-tabmenu 和 .p-tabmenu-nav 的樣式,確保正確的滾動行為。
    添加了按鈕的樣式,使其在各種狀態下都能正確顯示。

  8. 在 onMounted 鉤子中使用 nextTick,確保在 DOM 更新後再進行初始檢查。

樣式調整

一些深度選擇器 (:deep()) 來確保我們的樣式能夠覆蓋 PrimeVue 的默認樣式。

:deep(.p-tabmenu .p-tabmenu-nav .p-tabmenuitem.p-highlight .p-menuitem-link) 選擇器中的相應屬性。

scroll

scrollIntoView 選項中,這樣可以確保選中的 tab 總是居中顯示。

全部程式碼

<template>
  <div class="card">
    <div class="scroll-container" ref="scrollContainer">
      <p-tabMenu :model="items" @tab-change="onTabChange" ref="tabMenu">
        <template #item="{ item, props }">
          <a
            v-ripple
            v-bind="props.action"
            class="flex align-items-center gap-2"
          >
            <img
              :alt="item.name"
              :src="`https://primefaces.org/cdn/primevue/images/avatar/${item.image}`"
              style="width: 32px"
            />
            <span class="font-bold">{{ item.name }}</span>
          </a>
        </template>
      </p-tabMenu>
      <p-button
        class="scroll-button left p-button-rounded p-button-text"
        @click="scroll('left')"
        :disabled="!showLeftScroll"
      >
        <i class="pi pi-chevron-left"></i>
      </p-button>
      <p-button
        class="scroll-button right p-button-rounded p-button-text"
        @click="scroll('right')"
        :disabled="!showRightScroll"
      >
        <i class="pi pi-chevron-right"></i>
      </p-button>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick, watchEffect } from "vue";

const items = ref([
  { name: "Amy Elsner", image: "amyelsner.png" },
  { name: "Anna Fali", image: "annafali.png" },
  { name: "Asiya Javayant", image: "asiyajavayant.png" },
  { name: "Bernardo Dominic", image: "bernardodominic.png" },
  { name: "Elwin Sharvill", image: "elwinsharvill.png" },
  { name: "Ioni Bowcher", image: "ionibowcher.png" },
  { name: "Ivan Magalhaes", image: "ivanmagalhaes.png" },
  { name: "Onyama Limba", image: "onyamalimba.png" },
  { name: "Stephen Shaw", image: "stephenshaw.png" },
  { name: "XuXue Feng", image: "xuxuefeng.png" },
]);

const scrollContainer = ref(null);
const tabMenu = ref(null);
const showLeftScroll = ref(false);
const showRightScroll = ref(false);

const checkScroll = () => {
  if (tabMenu.value && tabMenu.value.$el) {
    const menuElement = tabMenu.value.$el.querySelector(".p-tabmenu-nav");
    const { scrollLeft, scrollWidth, clientWidth } = menuElement;
    showLeftScroll.value = scrollLeft > 0;
    showRightScroll.value = scrollLeft < scrollWidth - clientWidth;
  }
};

const scroll = (direction) => {
  if (tabMenu.value && tabMenu.value.$el) {
    const menuElement = tabMenu.value.$el.querySelector(".p-tabmenu-nav");
    const scrollAmount = 200;
    if (direction === "left") {
      menuElement.scrollLeft -= scrollAmount;
    } else {
      menuElement.scrollLeft += scrollAmount;
    }
    nextTick(checkScroll);
  }
};

const onTabChange = (event) => {
  nextTick(() => {
    const tabElement = event.originalEvent.target.closest(".p-tabmenuitem");
    if (tabElement) {
      tabElement.scrollIntoView({
        block: "nearest",
      });
    }
    checkScroll();
  });
};

const onResize = () => {
  checkScroll();
  onTabChange({
    originalEvent: { target: tabMenu.value.$el.querySelector(".p-highlight") },
  });
};

watchEffect(() => {
  if (scrollContainer.value) {
    window.addEventListener("resize", onResize);
  }
});

onMounted(() => {
  nextTick(() => {
    checkScroll();
    window.addEventListener("resize", checkScroll);
  });
});
</script>

<style scoped>
.scroll-container {
  position: relative;
  overflow: hidden;
  padding: 0 40px; /* Make room for buttons */
}

.scroll-button {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
}

.scroll-button.left {
  left: 0;
}

.scroll-button.right {
  right: 0;
}

:deep(.p-tabmenu) {
  overflow-x: hidden;
}

:deep(.p-tabmenu-nav) {
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* Internet Explorer 10+ */
  scroll-behavior: smooth;
  padding-bottom: 1px; /* Add padding to show the bottom border */
}

:deep(.p-button.scroll-button) {
  color: #333 !important;
}

:deep(.p-highlight .p-menuitem-link) {
  border-color: #10b981;
}

/* 防止文字斷行 */
:deep(.p-tabmenu-nav .p-menuitem-link) {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

:deep(.p-tabmenu-nav .p-menuitem-link) span {
  margin-left: 10px;
}

:deep(.p-button:disabled) {
  color: #dfdada !important;
}
</style>


上一篇
Day23-Chart.js 圖表
下一篇
Day25 - Breadcrumb 麵包屑導航
系列文
深入探索PrimeVue 套件及元件寫法29
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言